﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace PrattParsing
{
    public class RegexScanner : IScanner, IEnumerable<Token>
    {
        public delegate Token TokenCreator(string token, int type, TextPos pos);

        public const int ERROR_TYPE = -1;
        private const int EOF_TYPE = -2;

        TokenRegex[] regexes;
        List<TokenRegex> regexList;

        ITokenFilter[] filters;
        List<ITokenFilter> filterList;

        IEnumerable<Token> tokenStream;
        IEnumerator<Token> tokenEnumerator;
        TokenCreator creator;

        string input;
        int[] types;

        public RegexScanner(string input)
        {
            regexList = new List<TokenRegex>();
            filterList = new List<ITokenFilter>();

            this.input = input;
            this.types = new int[input.Length];
            for (int i = 0; i < types.Length; i++)
                types[i] = ERROR_TYPE;
        }

        [System.Diagnostics.DebuggerNonUserCode]
        protected void setTokenCreator(TokenCreator creator)
        {
            this.creator = creator;
        }

        [System.Diagnostics.DebuggerNonUserCode]
        protected void addFilter(ITokenFilter filter)
        {
            filterList.Add(filter);
        }

        [System.Diagnostics.DebuggerNonUserCode]
        protected void addRegex(TokenRegex regex)
        {
            regexList.Add(regex);
        }
        [System.Diagnostics.DebuggerNonUserCode]
        protected void addRegex(string pattern, RegexOptions options, int tokenType)
        {
            regexList.Add(new TokenRegex(pattern, options, tokenType));
        }
        [System.Diagnostics.DebuggerNonUserCode]
        protected void addRegex(string pattern, int tokenType)
        {
            regexList.Add(new TokenRegex(pattern, RegexOptions.None, tokenType));
        }

        [System.Diagnostics.DebuggerNonUserCode]
        protected void compile()
        {
            regexes = regexList.ToArray();
            regexList = null;

            filters = filterList.ToArray();
            filterList = null;
        }

        protected void compute()
        {
            foreach (TokenRegex regex in regexes)
                foreach (Match match in Regex.Matches(input, regex.pattern, regex.options))
                    for (int i = 0; i < match.Length; i++)
                        types[match.Index + i] = regex.tokenType;

            tokenStream = this;
            foreach (ITokenFilter filter in filters)
                tokenStream = filter.Filter(tokenStream);

            tokenEnumerator = tokenStream.GetEnumerator();
        }

        //bool enumDone = false;
        [System.Diagnostics.DebuggerNonUserCode]
        public Token getToken()
        {
            if (tokenEnumerator.MoveNext())
                return tokenEnumerator.Current;
            else
                return Token.EOF(new TextPos(pos, line, col, 1));
                //throw new InvalidOperationException("No more tokens left in stream.");
        }

        #region Internal Token Computer
        int pos = 0;
        int line = 0, col = 0;

        [System.Diagnostics.DebuggerNonUserCode]
        void next()
        {
            col++;
            if (pos < input.Length && input[pos] == '\n')
            {
                line++;
                col = 0;
            }
            pos++;
        }

        [System.Diagnostics.DebuggerNonUserCode]
        int type(int index)
        {
            return index < types.Length ? types[index] : EOF_TYPE;
        }

        bool done = false;
        [System.Diagnostics.DebuggerNonUserCode]
        private Token enumeratorGetToken()
        {
            if (done)
                return Token.EOF(new TextPos(pos, line, col, 1));
                //throw new InvalidOperationException("No more tokens left in stream.");

            int tokenType = type(pos);
            if (tokenType == EOF_TYPE)
            {
                done = true;
                return Token.EOF(new TextPos(pos, line, col, 1));
            }
            int curPos = pos, curLine = line, curCol = col;
            int tokenLength = 1;
            next();
            while (type(pos) == tokenType)
            {
                tokenLength++;
                next();
            }
            return creator(input.Substring(curPos, tokenLength),
                tokenType, new TextPos(curPos, curLine, curCol, tokenLength));
        }
        #endregion

        #region Enumerable Stuff
        [System.Diagnostics.DebuggerNonUserCode]
        public IEnumerator<Token> GetEnumerator()
        {
            while (true)
            {
                yield return enumeratorGetToken();
            }
        }
        [System.Diagnostics.DebuggerNonUserCode]
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return null;
            //throw new NotImplementedException();
        }
        #endregion

        [System.Diagnostics.DebuggerNonUserCode]
        struct TokenChar
        {
            public char c;
            public int type;

            public TokenChar(char c, int type)
            {
                this.c = c;
                this.type = type;
            }
        }
    }

    [System.Diagnostics.DebuggerNonUserCode]
    public class TokenRegex
    {
        public string pattern;
        public RegexOptions options;
        public int tokenType;

        public TokenRegex(string pattern, RegexOptions options, int tokenType)
        {
            this.pattern = pattern;
            this.options = options;
            this.tokenType = tokenType;

            this.options &= RegexOptions.Compiled;
        }
    }

    public interface ITokenFilter
    {
        IEnumerable<Token> Filter(IEnumerable<Token> stream);
    }

    //public interface ITokenStream : IEnumerable<Token> { }
}
